
/**
 * Copyright 2009 cestum.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package org.waveprotocol.wave.model.document.operation.util {

import mx.collections.IViewCursor;

import org.goverla.collections.ArrayList;
import org.goverla.collections.HashMap;
import org.waveprotocol.wave.model.document.operation.impl.AnnotationMap;
import org.waveprotocol.wave.model.document.operation.impl.AnnotationsUpdate;
import org.waveprotocol.wave.model.util.Preconditions;


public class ImmutableAnnotationsStateMap {
	
	private var attriMap:HashMap;
	
	private var attributes:ArrayList;
	
	//  private final Set entrySet = new AbstractSet() {
	//
	//        
	//        public Iterator iterator() {
	//          return new Iterator() {
	//            private Iterator iterator = attributes.iterator();
	//            public boolean hasNext() {
	//              return iterator.hasNext();
	//            }
	//            public Attribute next() {
	//              Object obj = iterator.next();
	//              return (Attribute) obj;
	//            }
	//            public void remove() {
	//              throw new UnsupportedOperationException("Attempt to modify an immutable set.");
	//            }
	//          };
	//        }
	//
	//        
	//        public int size() {
	//          return attributes.size();
	//        }
	//
	//      };
	
	public function ImmutableAnnotationsStateMap(attributes:ArrayList) {
		if (attributes == null) {
			this.attributes = new ArrayList();
			this.attriMap = new HashMap();
		} else { 
			this.attributes = attributes;
			this.attriMap = getMap(attributes);
		}
	}
	
	private static function getMap(attributes2:ArrayList):HashMap {
		var size:int= attributes2.length;
		var attributeMap:HashMap= new HashMap();
		for (var i:int=0; i< size; i++ ) {
			try {
				var temp:Attribute= Attribute(attributes2.getItemAt(i));
				attributeMap.addItem(temp.name, temp.value);  	
			} catch(e:Error) {
			}
		}
		return attributeMap;
	}
	//  /**
	//   * Creates a new T object containing no T.
	//   */
	//  public ImmutableStateMap() {
	//    attributes = new ArrayList();
	//  }
	
	//
	//  private static final Comparator comparator = new Comparator() {
	//    
	//    public int compare(Attribute a, Attribute b) {
	//      return a.name.compareTo(b.name);
	//    }
	//  };
	
	//  /**
	//   * Constructs a new <code>T</code> object with the T
	//   * specified by the given mapping.
	//   *
	//   * @param map The mapping of attribute names to attribute values.
	//   */
	//  public ImmutableStateMap(Map map) {
	//    this.attributes = attributeListFromMap(map);
	//  }
	
	//  public ImmutableStateMap(String ... pairs) {
	//    if (pairs.length % 2 != 0) {
	//      throw new Error("Pairs must contain an even number of strings");
	//    }
	//
	//    Map<String, String> map = new HashMap<String, String>();
	//
	//    for (int i = 0; i < pairs.length; i += 2) {
	//      Preconditions.checkNotNull(pairs[i], "Null key");
	//      Preconditions.checkNotNull(pairs[i + 1], "Null value");
	//      if (map.containsKey(pairs[i])) {
	//        throw new Error("Duplicate key: " + pairs[i]);
	//      }
	//      map.put(pairs[i], pairs[i + 1]);
	//    }
	//
	//    this.attributes = attributeListFromMap(map);
	//  }
	
	public static function attributeListFromMap(map:HashMap):ArrayList {
		var attributeList:ArrayList= new ArrayList();
		var keys:Array = map.keys;
		for (var i:int=0; i<keys.length; i++) {
			var key:String = keys[i];
			if (key == null || map.getItem(key) == null) {
				throw new Error("Null POinter: This map does not allow null keys or values");
			}
			attributeList.addItem(new Attribute(key, String(map.getItem(key))));    	
		}
		//FIXME
		//    Collections.sort(attributeList, comparator);
		return attributeList;
	}
	
	// Public method that is overridden by specifying a different # of arguments  
	public function updateWith(attributeUpdate:AnnotationsUpdate, checkCompatibility:Boolean=false):AnnotationMap {  
		// figure out how many parameters we were passed in  
		// by arguments.length, and call the appropriate  
		// helper by the function.apply()... the help panel has  
		// good documentation about this.  
		return  this["updateWith"+arguments.length].apply(this, arguments);  
	}  
	
	/**
	 * Returns a <code>T</code> object obtained by applying the update
	 * specified by the <code>Object</code> object into this
	 * <code>T</code> object.
	 *
	 * @param attributeUpdate The update to apply.
	 * @return A <code>T</code> object obtained by applying the given
	 *         update onto this object.
	 */
	public function updateWith1(attributeUpdate:AnnotationsUpdate):AnnotationMap {
		return updateWith2(attributeUpdate, true);
	}
	
	public function updateWithNoCompatibilityCheck(attributeUpdate:AnnotationsUpdate):AnnotationMap {
		return updateWith2(attributeUpdate, false);
	}
	
	private function updateWith2(annotationUpdate:AnnotationsUpdate, checkCompatibility:Boolean):AnnotationMap {
		var newImmutableStateMap:ArrayList= new ArrayList();
		var iterator:IViewCursor= attributes.createCursor();
		var nextAttribute:Attribute= Attribute(iterator.current);
		// TODO: Have a slow path when the cast would fail.
		//    List<AttributeUpdate> updates = (ImmutableUpdateMap(attributeUpdate)).updates;
		var temList:ArrayList = (ImmutableAnnotationsUpdateMap(annotationUpdate)).updates;
		var size1:int = temList.length;
		for (var i1:int=0; i1< size1; i1++ ) {			
			var update:AttributeUpdate = temList.getItemAt(i1) as AttributeUpdate;
			while (nextAttribute != null) {
				var comparison:int= update.name.localeCompare(nextAttribute.name);
				if (comparison > 0) {
					newImmutableStateMap.addItem(nextAttribute);
					nextAttribute = Attribute(iterator.current);
					iterator.moveNext();
				} else if (comparison < 0) {
					if (checkCompatibility && update.oldValue != null) {
						throw new Error(
							"Mismatched old value: attempt to update unset attribute with " + update);
					}
					break;
				} else if (comparison == 0) {
					if (checkCompatibility && !nextAttribute.value == update.oldValue) {
						throw new Error("Mismatched old value: attempt to update " +
							nextAttribute + " with " + update);
					}
					nextAttribute = Attribute(iterator.current);
					iterator.moveNext();
					break;
				}
			}
			if (update.newValue != null) {
				newImmutableStateMap.addItem(new Attribute(update.name, update.newValue));
			}
		}
		if (nextAttribute != null) {
			newImmutableStateMap.addItem(nextAttribute);
			while (!iterator.afterLast) {
				newImmutableStateMap.addItem(iterator.current);
				iterator.moveNext();
			}
		}
		return createFromList(newImmutableStateMap);
	}
	
	protected function createFromList(attributes:ArrayList):AnnotationMap{return null} ;
	
	public static function checkAttributesSorted(attributes:ArrayList):void {
		var previous:Attribute= null;
		//    ArrayList<Attribute> attr = attributes;
		for (var a1:Object in attributes) {
			var a:Attribute = Attribute(a1);
			Preconditions.checkNotNull(a, "Null attribute");
			org.waveprotocol.wave.model.util.Assert.assert(a.name != null);
			org.waveprotocol.wave.model.util.Assert.assert(a.value != null);
			if (previous != null) {
				Preconditions.checkArgument(previous.name.localeCompare(a.name) < 0,
					"Attribute keys not strictly monotonic: " + previous.name + ", " + a.name);
			}
			previous = a;
		}
	}
	
	public static function updateWithoutCompatibilityCheck(state:Object, update:AnnotationsUpdate):AnnotationMap {
		// the cast below is required to work with javac from OpenJDK 7
		return (ImmutableAnnotationsStateMap(state)).updateWith(update, false);
	}
	
	
	public function getValue(key:String):String {
		return String(attriMap.getItem(key));
	}
	
	public function keySet():Array {
		return attriMap.keys;
	}
	
	public function isEmpty():Boolean {
		return attriMap.keys.length == 0;
	}
	
	public function containsKey(key:String):Boolean {
		return attriMap.containsKey(key);
	}
	
	public function size():int {
		return attriMap.keys.length;
}
}
}